//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2013 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_AUTO_INTRUSIVE_PTR_H_
#define _BUOLA_AUTO_INTRUSIVE_PTR_H_

#include <memory>
#include <cstddef>
#include <cstdint>
#include <climits>
#include <sys/types.h>

namespace buola {

///\addtogroup memory
///@{

/////////////////////////////////////////////////////////////////////////////////////////////////
///
/// intrusive pointer for reference-counted shared objects
///
/// \c intrusive_ptr is a pointer for shared reference-counted objects. It differs from
/// \c std::shared_ptr in that the reference count is embedded into the object, which is less
/// flexible but more efficient.
///
/// To support intrusive pointers, a class must expose the following member functions:
/// - \c IntrusiveAddRef to increment the reference count
/// - \c IntrusiveRemoveRef to decrement the reference count and delete the object when the
/// count reaches zero.
///
/// The simplest way to support intrusive pointers is to inherit from \ref UShared.
///
/// This differs from \c boost::intrusive_ptr in that the support functions are member functions
/// while in boost free functions are used. The approach used here is less flexible, since it
/// is not possible to add intrusive pointer support to existing classes, but it provides better
/// encapsulation, since it is possible to make the functions private, so that it is only
/// possible to modify the reference count from an \c intrusive_ptr. If the other aprroach is
/// needed, use \c boost::intrusive_ptr.
///
/////////////////////////////////////////////////////////////////////////////////////////////////

template<typename tType>
class intrusive_ptr
{
public:
    ///all smart pointers must have this typedef
    typedef tType element_type;
    
    ///initializes the pointer
    ///
    ///Unless \p pAdd is \c false, the reference count of the object is incremented.
    intrusive_ptr(tType *pPtr=nullptr,bool pAdd=true)
        :   mPtr(pPtr)
    {
        if(mPtr&&pAdd) mPtr->IntrusiveAddRef();
    }

    ///initialization from an intrusive pointer to a compatible type
    template<typename tOther>
    intrusive_ptr(const intrusive_ptr<tOther> &pRH)
        :   mPtr(pRH.get())
    {
        if(mPtr) mPtr->IntrusiveAddRef();
    }

    ///copy constructor
    intrusive_ptr(const intrusive_ptr &pRH)
        :   mPtr(pRH.mPtr)
    {
        if(mPtr) mPtr->IntrusiveAddRef();
    }

    ///move constructor
    ///
    ///Slightly more efficient than copy construction+destruction since it avoids incrementing
    ///and decrementing the reference count.
    intrusive_ptr(intrusive_ptr &&pRH)
        :   mPtr(pRH.mPtr)
    {
        pRH.mPtr=nullptr;
    }

    ///destructor
    ///
    ///Decrements the reference count of the object.
    ~intrusive_ptr()
    {
        if(mPtr) mPtr->IntrusiveRemoveRef();
    }
    
    ///assignment operator
    intrusive_ptr &operator=(tType *pPtr)
    {
        intrusive_ptr(pPtr).swap(*this);
        return *this;
    }

    ///assignment operator
    template<typename tOther>
    intrusive_ptr &operator=(const intrusive_ptr<tOther> &pRH)
    {
        intrusive_ptr(pRH).swap(*this);
        return *this;
    }

    ///copy assignment operator
    intrusive_ptr &operator=(const intrusive_ptr &pRH)
    {
        if(pRH.mPtr!=mPtr)
            intrusive_ptr(pRH).swap(*this);
        return *this;
    }

    ///move assignment operator
    intrusive_ptr &operator=(intrusive_ptr &&pRH)
    {
        pRH.swap(*this);
        return *this;
    }

    ///resets to point to another object
    void reset(tType *pRH=nullptr)
    {
        intrusive_ptr(pRH).swap(*this);
    }
    
    ///returns a reference to the pointed-to object
    tType &operator*() const
    {
        return *mPtr;
    }
    
    ///returns a raw pointer to the pointed-to object
    tType *operator->() const
    {
        return mPtr;
    }
    
    ///returns a raw pointer to the pointed-to object
    tType *get() const
    {
        return mPtr;
    }
    
    ///explicit conversion to bool
    ///
    ///Returns true if the pointer points to some object.
    explicit operator bool() const
    {
        return mPtr;
    }
    
    ///implementation of the swap of two \c intrusive_ptr
    ///
    ///Just swaps the underlying raw pointers.
    void swap(intrusive_ptr &pOther) // never throws
    {
        std::swap(mPtr,pOther.mPtr);
    }
    
private:
    tType *mPtr;
};

template<typename tType>
inline void swap(intrusive_ptr<tType> &p1,intrusive_ptr<tType> &p2)
{
    p1.swap(p2);
}

template<typename tLH,typename tRH>
inline bool operator==(const intrusive_ptr<tLH> &pLH,const intrusive_ptr<tRH> &pRH)
{
    return pLH.get()==pRH.get();
}

template<typename tLH,typename tRH>
inline bool operator!=(const intrusive_ptr<tLH> &pLH,const intrusive_ptr<tRH> &pRH)
{
    return pLH.get()!=pRH.get();
}

template<typename tLH,typename tRH>
inline bool operator==(const intrusive_ptr<tLH> &pLH,tRH *pRH)
{
    return pLH.get()==pRH;
}

template<typename tLH,typename tRH>
inline bool operator!=(const intrusive_ptr<tLH> &pLH,tRH *pRH)
{
    return pLH.get()!=pRH;
}

template<typename tLH,typename tRH>
inline bool operator==(tLH *pLH,const intrusive_ptr<tRH> &pRH)
{
    return pRH.get()==pLH;
}

template<typename tLH,typename tRH>
inline bool operator!=(tLH *pLH,const intrusive_ptr<tRH> &pRH)
{
    return pRH.get()!=pLH;
}

///@}

/*namespace buola*/ }

namespace std {
    template<typename tType>
    struct hash<buola::intrusive_ptr<tType>> : public std::hash<tType*>
    {
        std::size_t operator()(const buola::intrusive_ptr<tType> &pPtr) const
        {
            return std::hash<tType*>::operator()(pPtr.get());
        }
    };
/*namespace std*/ }

#endif
